//! Boa's **`boa_icu_provider`** exports the default data provider used by its `Intl` implementation.
//!
//! # Crate Overview
//! This crate exports the function `buffer`, which contains an extensive dataset of locale data to
//! enable `Intl` functionality in the engine. The set of locales included is precisely the ["modern"]
//! subset of locales in the [Unicode Common Locale Data Repository][cldr].
//!
//! If you need to support the full set of locales, you can check out the [ICU4X guide] about
//! generating custom data providers. Boa supports plugging any [`BufferProvider`]s
//! generated by the tool.
//!
//! ["modern"]: https://github.com/unicode-org/cldr-json/tree/main/cldr-json/cldr-localenames-modern/main
//! [cldr]: https://github.com/unicode-org/
//! [ICU4X guide]: https://github.com/unicode-org/icu4x/blob/main/docs/tutorials/data_management.md
//! [`BufferProvider`]: icu_provider::buf::BufferProvider
#![doc = include_str!("../ABOUT.md")]
#![doc(
    html_logo_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo_black.svg",
    html_favicon_url = "https://raw.githubusercontent.com/boa-dev/boa/main/assets/logo_black.svg"
)]
#![cfg_attr(not(feature = "std"), no_std)]

extern crate alloc;
use core::fmt::Debug;

use icu_locale::LocaleFallbacker;
use icu_provider::prelude::*;
use icu_provider_adapters::{fallback::LocaleFallbackProvider, fork::MultiForkByMarkerProvider};
use icu_provider_blob::BlobDataProvider;
use once_cell::sync::{Lazy, OnceCell};

/// A buffer provider that is lazily deserialized at the first data request.
///
/// The provider must specify the list of keys it supports, to avoid deserializing the
/// buffer for unknown keys.
struct LazyBufferProvider {
    provider: OnceCell<BlobDataProvider>,
    bytes: &'static [u8],
    valid_markers: &'static [DataMarkerInfo],
}

impl Debug for LazyBufferProvider {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("LazyBufferProvider")
            .field("provider", &self.provider)
            .field("bytes", &"[...]")
            .field("valid_keys", &self.valid_markers)
            .finish()
    }
}

impl DynamicDataProvider<BufferMarker> for LazyBufferProvider {
    fn load_data(
        &self,
        marker: DataMarkerInfo,
        req: DataRequest<'_>,
    ) -> Result<DataResponse<BufferMarker>, DataError> {
        if !self.valid_markers.contains(&marker) {
            return Err(DataErrorKind::MarkerNotFound.with_marker(marker));
        }

        let Ok(provider) = self
            .provider
            .get_or_try_init(|| BlobDataProvider::try_new_from_static_blob(self.bytes))
        else {
            return Err(DataErrorKind::Custom.with_str_context("invalid blob data provider"));
        };

        provider.load_data(marker, req)
    }
}

impl DynamicDryDataProvider<BufferMarker> for LazyBufferProvider {
    fn dry_load_data(
        &self,
        marker: DataMarkerInfo,
        req: DataRequest<'_>,
    ) -> Result<DataResponseMetadata, DataError> {
        if !self.valid_markers.contains(&marker) {
            return Err(DataErrorKind::MarkerNotFound.with_marker(marker));
        }

        let Ok(provider) = self
            .provider
            .get_or_try_init(|| BlobDataProvider::try_new_from_static_blob(self.bytes))
        else {
            return Err(DataErrorKind::Custom.with_str_context("invalid blob data provider"));
        };

        provider.dry_load_data(marker, req)
    }
}

/// A macro that creates a [`LazyBufferProvider`] from an icu4x crate.
macro_rules! provider_from_icu_crate {
    ($service:path) => {
        paste::paste! {
            LazyBufferProvider {
                provider: OnceCell::new(),
                bytes: include_bytes!(concat!(
                    env!("CARGO_MANIFEST_DIR"),
                    "/data/",
                    stringify!($service),
                    ".postcard",
                )),
                valid_markers: $service::provider::MARKERS,
            }
        }
    };
}

/// Boa's default buffer provider.
static PROVIDER: Lazy<LocaleFallbackProvider<MultiForkByMarkerProvider<LazyBufferProvider>>> =
    Lazy::new(|| {
        let provider = MultiForkByMarkerProvider::new(alloc::vec![
            provider_from_icu_crate!(icu_casemap),
            provider_from_icu_crate!(icu_collator),
            provider_from_icu_crate!(icu_datetime),
            provider_from_icu_crate!(icu_decimal),
            provider_from_icu_crate!(icu_list),
            provider_from_icu_crate!(icu_locale),
            provider_from_icu_crate!(icu_normalizer),
            provider_from_icu_crate!(icu_plurals),
            provider_from_icu_crate!(icu_segmenter),
        ]);
        let fallbacker = LocaleFallbacker::try_new_with_buffer_provider(&provider)
            .expect("The statically compiled data file should be valid.");
        LocaleFallbackProvider::new(provider, fallbacker)
    });

#[derive(Debug)]
struct Wrapper<T: 'static>(&'static T);

impl<T> DynamicDataProvider<BufferMarker> for Wrapper<T>
where
    T: DynamicDataProvider<BufferMarker>,
{
    fn load_data(
        &self,
        marker: DataMarkerInfo,
        req: DataRequest<'_>,
    ) -> Result<DataResponse<BufferMarker>, DataError> {
        self.0.load_data(marker, req)
    }
}

impl<T> DynamicDryDataProvider<BufferMarker> for Wrapper<T>
where
    T: DynamicDryDataProvider<BufferMarker>,
{
    fn dry_load_data(
        &self,
        marker: DataMarkerInfo,
        req: DataRequest<'_>,
    ) -> Result<DataResponseMetadata, DataError> {
        self.0.dry_load_data(marker, req)
    }
}

/// Gets the default data provider stored as a [`DynamicDryDataProvider<BufferMarker>`].
///
/// [`DynamicDryDataProvider<BufferMarker>`]: icu_provider::DynamicDryDataProvider
#[must_use]
pub fn buffer() -> impl DynamicDryDataProvider<BufferMarker> {
    Wrapper(&*PROVIDER)
}
